/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.objectweb.asm.ClassVisitor;

/**
 * A replacement specifies an action for a {@link UnitBuilder} to take in transforming the bytecodes
 * of a class it generates.
 *
 * @param <U> the type of the unit class
 */
public abstract class Replacement<U>
{
    public static Object NOT_MATCHED = new Object();

    private static int nextId = 1;
    private static final Map<Integer, Replacement<?>> replacements = new WeakHashMap<Integer, Replacement<?>>();

    protected Class<U> unitClass;
    protected int id;
    protected List<Method> methods = new ArrayList<Method>();

    public static synchronized Object match( int id, Object context )
    {
        Replacement<?> replacement = replacements.get( id );
        if (replacement.matches( context ))
            return replacement.getValue();
        return NOT_MATCHED;
    }

    protected Replacement(Class<U> unitClass)
    {
        this.unitClass = unitClass;
        synchronized( Replacement.class )
        {
            replacements.put( nextId, this );
            this.id = nextId++;
        }
    }

    /**
     * Returns an instance of the unit through which method calls specify the methods on the unit
     * within which replacements may occur. If no methods are specified replacements occur on all
     * methods.
     *
     * @return
     */
    public U inMethod()
    {
        return ProxyBuilder.of( unitClass, new MethodInterceptor() {

            public Object intercept( InvocationContext context ) throws Throwable
            {
                methods.add( context.getMethod() );
                return null;
            }
        }).create();
    }

    /**
     * Specifies a method on the unit within which replacements may occur. If no methods are
     * specified replacements occur on all methods.
     *
     * @param unitMethod
     * @return
     */
    public Replacement<U> in(Method unitMethod)
    {
        methods.add( unitMethod );
        return this;
    }

    Object getValue2()
    {
        return null;
    }

    abstract Object getValue();

    abstract boolean matches( Object context );

    abstract ClassVisitor chain( ClassVisitor visitor );
}
